"""
Task Schedule v0.1
Copyright 2006  kmsoft@gmail.com

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from time import time as _time, strftime, localtime, sleep
from heapq import heappush, heappop

TASK_ONCE   = 0     # run only once
TASK_PERIOD = 1     # run periodicly
TASK_SPAN   = 2     # run spanned
TASK_EXIT   = 4     # exit logo. When got the logo, Thread should exit now.

class Empty(Exception):
    "Exception raised by Queue.get(block=0)/get_nowait()."
    pass

class Full(Exception):
    "Exception raised by Queue.put(block=0)/put_nowait()."
    pass

class InvalidTask(Exception):
    "Exception raised by DQueue.put(). Item should be DealyedTask or subclass"
    pass

class DelayedTask:
    """
    Delayed Task
    """
    def __init__(self, startTime, sleepTime, name='Delayed Task', taskMode=TASK_PERIOD):
        """
        startTime: the time the task to run
        sleepTime: sleep time
        name: Task's name
        taskMode: task run mode
        """
        t = _time()
        if startTime is None or startTime <= 0:
            self.startTime = t
        else:
            self.startTime = startTime
        self.sleepTime = sleepTime
        self.taskMode = taskMode
        self.name = name

    def update(self, startTime=None):
        t = _time()
        if startTime is None or startTime < t:
            self.startTime = t + self.sleepTime
        else:
            self.startTime = startTime

    def delay(self):
        return self.startTime - _time()

    def taskMode(self):
        return self.taskMode

    def isExit(self):
        return self.taskMode == TASK_EXIT

    def run(self):
        """
        the method should be implemented by subclass.
        """
        pass

    def preRun(self, dq):
        if self.taskMode == TASK_PERIOD:
            self.update()
            dq.put(self)

    def postRun(self, dq):
        if self.taskMode == TASK_SPAN:
            self.update()
            dq.put(self)

    def __cmp__(self, other):
        sd = self.delay()
        od = other.delay()
        if sd <= od:
            return -1
        return 1

    def __repr__(self):
        s = strftime('%Y-%m-%d %H:%M:%S', localtime(self.startTime))
        mode = ''
        if self.taskMode == TASK_ONCE:
            mode = 'Once'
        elif self.taskMode == TASK_PERIOD:
            mode = 'Periodic'
        else:
            mode = 'Spanned'
        return "Name: %s Start: %s Sleep: %d Mode: %s" % (self.name, s, self.sleepTime, mode)

# the exit logo. thread should shutdown quickly when got the logo.
ExitTask = DelayedTask(0, 0, 'Exit Task', TASK_EXIT)

# the main code of PQueue was copied from the Queue.py of Python 2.5
class PQueue:
    """
    Priority Queue
    """
    def __init__(self, maxsize=0):
        try:
            import threading
        except ImportError:
            import dummy_threading as threading
        self._init(maxsize)
        self.mutex = threading.Lock()
        self.not_empty = threading.Condition(self.mutex)
        self.not_full = threading.Condition(self.mutex)

    def qsize(self):
        """Return the approximate size of the queue (not reliable!)."""
        self.mutex.acquire()
        n = self._qsize()
        self.mutex.release()
        return n

    def empty(self):
        self.mutex.acquire()
        n = self._empty()
        self.mutex.release()
        return n

    def full(self):
        self.mutex.acquire()
        n = self._full()
        self.mutex.release()
        return n

    def put(self, item, block=True, timeout=None):
        self.not_full.acquire()
        try:
            if not block:
                if self._full():
                    raise Full
            elif timeout is None:
                while self._full():
                    self.not_full.wait()
            else:
                if timeout < 0:
                    raise ValueError("'timeout' must be a positive number")
                endtime = _time() + timeout
                while self._full():
                    remaining = endtime - _time()
                    if remaining <= 0.0:
                        raise Full
                    self.not_full.wait(remaining)
            self._put(item)
            self.not_empty.notify()
        finally:
            self.not_full.release()

    def get(self, block=True, timeout=None):
        self.not_empty.acquire()
        try:
            if not block:
                if self._empty():
                    raise Empty
            elif timeout is None:
                while self._empty():
                    self.not_empty.wait()
            else:
                if timeout < 0:
                    raise ValueError("'timeout' must be a positive number")
                endtime = _time() + timeout
                while self._empty():
                    remaining = endtime - _time()
                    if remaining <= 0.0:
                        raise Empty
                    self.not_empty.wait(remaining)
            item = self._get()
            self.not_full.notify()
            return item
        finally:
            self.not_empty.release()

    def _init(self, maxsize):
        self.maxsize = maxsize
        self.queue = []

    def _qsize(self):
        return len(self.queue)

    # Check whether the queue is empty
    def _empty(self):
        return not self.queue

    # Check whether the queue is full
    def _full(self):
        return self.maxsize > 0 and len(self.queue) == self.maxsize

    # Put a new item in the queue
    def _put(self, item):
        heappush(self.queue, item)

    # Get an item from the queue
    def _get(self):
        return heappop(self.queue)

class DQueue(PQueue):
    """
    Delayed Queue
    the main methods are take() and put()
    """
    def __init__(self, maxsize=0):
        PQueue.__init__(self, maxsize)

    def put(self, item):
        if not isinstance(item, DelayedTask):
            raise InvalidTask
        PQueue.put(self, item)

    def take(self):
        self.not_empty.acquire()
        try:
            while True:
                if self._empty():
                    self.not_empty.wait()
                else:
                    task = self.queue[0]
                    delay = task.delay()
                    if delay <= 0:
                        return self._get()
                    else:
                        self.not_empty.wait(delay)
        finally:
            self.not_empty.release()
